/**
 * <copyright>
 * </copyright>
 *
 * $Id$
 */
package org.eclipse.iota.model;

import static java.math.BigInteger.ONE;
import static java.math.BigInteger.ZERO;

import java.math.BigInteger;
import java.util.Random;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.iota.IotaPackage;

/**
 * @see org.eclipse.iota.IotaPackage#getConstant()
 * @model kind="class"
 * @generated
 */
public class Constant extends EObjectImpl implements Expression {
		
	/**
	 * Seeds for generating random constants.
	 */
	private final static Random RANDOM = new Random();

	/**
	 * Constructor.
	 * @param value Value.
	 */
	public Constant(BigInteger value) {
		super();
		setValue(value);
	}
	
	/**
	 * Constructor.
	 * @param value Value.
	 */
	public Constant(long value) {
		this(BigInteger.valueOf(value));
	}
	
	/**
	 * Negate the value of this constant.
	 * This changes the value of this constant.
	 */
	public Constant negate() {
		setValue(getValue().negate());
		return this;
	}
	
	/**
	 * Add something to the value of this constant.
	 * This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant add(Constant argument) {
		setValue(getValue().add(argument.getValue()));
		return this;
	}
	
	/**
	 * Subtract something from the value of this constant.
	 * This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant subtract(Constant argument) {
		setValue(getValue().subtract(argument.getValue()));
		return this;
	}
		
	/**
	 * Increase the value of this constant by one.
	 */
	public Constant increase() {
		setValue(getValue().add(ONE));
		return this;
	}

	/**
	 * Decrease the value of this constant by one.
	 */
	public Constant decrease() {
		setValue(getValue().subtract(ONE));		
		return this;
	}
	
	/**
	 * Multiply the value of this constant by the argument.
	 * This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant multiply(Constant argument) {
		setValue(getValue().multiply(argument.getValue()));
		return this;
	}
	
	/**
	 * Divide the value of this constant by the argument.
	 * This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant divide(Constant argument) {
		setValue(getValue().divide(argument.getValue()));
		return this;
	}
	
	/**
	 * Take the modulus of the value of this constant 
	 * to the argument. This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant mod(Constant argument) {
		setValue(getValue().mod(argument.getValue()));
		return this;
	}
	
	/**
	 * Take the power of this constant.
	 */
	public Constant pow(int exponent) {
		setValue(getValue().pow(exponent));
		return this;
	}
	
	/**
	 * Power with modulus.
	 */
	public Constant modPow(Constant exponent, Constant modulus) {
		setValue(getValue().modPow(exponent.getValue(), modulus.getValue()));
		return this;
	}

	/**
	 * Compute the greatest common divisor of this
	 * constant and the argument. This changes the value of this constant.
	 * @param argument Argument.
	 */
	public Constant gcd(Constant argument) {
		setValue(getValue().gcd(argument.getValue()));
		return this;
	}
	
	/**
	 * Change the value of this constant to its absolute value.
	 * @return This constant.
	 */
	public Constant abs() {
		setValue(getValue().abs());
		return this;
	}
	
	/**
	 * Get a copy of this constant. This does not
	 * change the current instance.
	 * @return A copy of this constant.
	 */
	public Constant copy() {
		return new Constant(getValue());
	}	
	
	/**
	 * Check if the value of this constant is zero.
	 * @return <code>true</code> if it is zero.
	 */
	public boolean isZero() {
		return ZERO.equals(getValue());
	}
	
	/**
	 * Check if the value of this constant is one.
	 * @return <code>true</code> if it is one.
	 */
	public boolean isOne() {
		return ONE.equals(getValue());
	}
	
	/**
	 * Check if the value of this constant is minus one.
	 * @return <code>true</code> if it is minus one.
	 */
	public boolean isMinusOne() {
		return (new BigInteger("-1")).equals(getValue());
	}
	
	/**
	 * Check if the value of this constant is positive.
	 * @return <code>true</code> if it is positive.
	 */
	public boolean isPositive() {
		return isGreaterThan(new Constant(ZERO));
	}

	/**
	 * Check if the value of this constant is negative.
	 * @return <code>true</code> if it is negative.
	 */
	public boolean isNegative() {
		return isLessThan(new Constant(ZERO));
	}
	
	/**
	 * Check if the value of this constant is greater than the argument.
	 * @return <code>true</code> if it is greater.
	 */
	public boolean isGreaterThan(Constant argument) {
		return getValue().compareTo(argument.getValue()) > 0;
	}
	
	/**
	 * Check if the value of this constant is less than the argument.
	 * @return <code>true</code> if it is less.
	 */
	public boolean isLessThan(Constant argument) {
		return getValue().compareTo(argument.getValue()) < 0;
	}
	
	/**
	 * Check if the value of this constant equals the argument.
	 * @return <code>true</code> if it is the same value.
	 */
	public boolean isEqualTo(Constant argument) {
		return getValue().compareTo(argument.getValue())==0;
	}
	
	/**
	 * Test whether the value of this constant fits into a byte.
	 * @return <code>true</code> if the value is a byte.
	 */
	public boolean isByte() {
		return getValue().bitLength() < Byte.SIZE;
	}
	
	/**
	 * Test whether the value of this constant fits into a short.
	 * @return <code>true</code> if the value is a short.
	 */
	public boolean isShort() {
		return getValue().bitLength() < Short.SIZE;
	}

	/**
	 * Test whether the value of this constant fits into an integer.
	 * @return <code>true</code> if the value is an integer.
	 */
	public boolean isInteger() {
		return getValue().bitLength() < Integer.SIZE;
	}

	/**
	 * Test whether the value of this constant fits into a long.
	 * @return <code>true</code> if the value is a long.
	 */
	public boolean isLong() {
		return getValue().bitLength() < Long.SIZE;
	}
	
	
	/**
	 * Create a random constant between the given lower and upper bound (inclusive).
	 * @param lower Lower bound or <code>null</code>.
	 * @param upper Upper bound or <code>null</code>.
	 * @return A new random constant.
	 */
	public static Constant random(Constant lower, Constant upper) {	
		
		// Initialize lower and upper bounds:
		int l = Byte.MIN_VALUE;
		int u = Byte.MAX_VALUE;
		
		if (lower!=null) {
			l = lower.getValue().intValue();
			if (upper==null) u = l + Byte.SIZE;
		}
		if (upper!=null) {
			u = upper.getValue().intValue();
			if (lower==null) l = u - Byte.SIZE;
		}
		
		// Check if lower bound is bigger than lower:
		if (u<l) {
			throw new RuntimeException("Lower bound bigger than upper: " + u + " < " + l);
		}
		
		// Compute the final result:
		int result = (u>l) ? RANDOM.nextInt(u-l)+l : l;
		return new Constant(result);
		
	}
	
	/**
	 * Create a new random constant. The value fits into a byte.
	 * @return new random constant.
	 */
	public static Constant random() {
		return random(null,null);
	}
	
	/**
	 * Get the equation that owns this expression.
	 */
	public Equation basicGetEquation() {
		if (eContainer() instanceof Equation) return (Equation) eContainer();
		if (eContainer() instanceof Expression) return ((Expression) eContainer()).getEquation();
		return null;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.emf.ecore.impl.BasicEObjectImpl#toString()
	 */
	@Override
	public String toString() {
		return String.valueOf(getValue());
	}
	
	/* ---------------------------------------------------------------- *
	 * GENERATED CODE.                                                  *
	 * Do not edit below this line. If you need to edit, move it above  *
	 * this line and change the '@generated'-tag to '@generated NOT'.   *
	 * ---------------------------------------------------------------- */	
	
	/**
	 * The default value of the '{@link #getValue() <em>Value</em>}' attribute.
	 * @see #getValue()
	 * @generated
	 * @ordered
	 */
	protected static final BigInteger VALUE_EDEFAULT = new BigInteger("0");
	
	/**
	 * The cached value of the '{@link #getValue() <em>Value</em>}' attribute.
	 * @see #getValue()
	 * @generated
	 * @ordered
	 */
	protected BigInteger value = VALUE_EDEFAULT;
	
	/**
	 * @generated
	 */
	public Constant() {
		super();
	}
	
	/**
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return IotaPackage.Literals.CONSTANT;
	}

	/**
	 * Returns the value of the '<em><b>Equation</b></em>' reference.
	 * @return the value of the '<em>Equation</em>' reference.
	 * @see org.eclipse.iota.IotaPackage#getExpression_Equation()
	 * @model transient="true" changeable="false" volatile="true" derived="true"
	 * @generated
	 */
	public Equation getEquation() {
		Equation equation = basicGetEquation();
		return equation != null && equation.eIsProxy() ? (Equation)eResolveProxy((InternalEObject)equation) : equation;
	}

	/**
	 * Returns the value of the '<em><b>Value</b></em>' attribute.
	 * @return the value of the '<em>Value</em>' attribute.
	 * @see #setValue(int)
	 * @see org.eclipse.iota.IotaPackage#getConstant_Value()
	 * @model
	 * @generated
	 */
	public BigInteger getValue() {
		return value;
	}

	/**
	 * Sets the value of the '{@link org.eclipse.iota.model.Constant#getValue <em>Value</em>}' attribute.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @param value the new value of the '<em>Value</em>' attribute.
	 * @see #getValue()
	 * @generated
	 */
	public void setValue(BigInteger newValue) {
		BigInteger oldValue = value;
		value = newValue;
		if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, IotaPackage.CONSTANT__VALUE, oldValue, value));
	}

	/**
	 * @generated
	 */
	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case IotaPackage.CONSTANT__EQUATION:
				if (resolve) return getEquation();
				return basicGetEquation();
			case IotaPackage.CONSTANT__VALUE:
				return getValue();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	/**
	 * @generated
	 */
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case IotaPackage.CONSTANT__VALUE:
				setValue((BigInteger)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	/**
	 * @generated
	 */
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case IotaPackage.CONSTANT__VALUE:
				setValue(VALUE_EDEFAULT);
				return;
		}
		super.eUnset(featureID);
	}

	/**
	 * @generated
	 */
	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case IotaPackage.CONSTANT__EQUATION:
				return basicGetEquation() != null;
			case IotaPackage.CONSTANT__VALUE:
				return VALUE_EDEFAULT == null ? value != null : !VALUE_EDEFAULT.equals(value);
		}
		return super.eIsSet(featureID);
	}

}
